package com.xbongbong.paas.script.controller;


import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.xbongbong.paas.constant.PaasConstant;
import com.xbongbong.paas.domain.entity.PaasFormExplainEntity;
import com.xbongbong.paas.elasticsearch.util.XbbElasticsearchRestTemplate;
import com.xbongbong.paas.enums.FieldTypeEnum;
import com.xbongbong.paas.enums.FlowStatusEnum;
import com.xbongbong.paas.field.FieldAttrEntity;
import com.xbongbong.paas.model.PaasFormExplainModel;
import com.xbongbong.paas.pojo.dto.FixDataJobDTO;
import com.xbongbong.paas.toolbox.exception.XbbException;
import com.xbongbong.paas.toolbox.util.CollectionsUtil;
import com.xbongbong.paas.toolbox.util.DateTimeUtil;
import com.xbongbong.paas.toolbox.util.StringUtil;
import com.xbongbong.pro.config.ProBaseConfig;
import com.xbongbong.pro.constant.XbbProConstant;
import com.xbongbong.pro.enums.errorcodes.SystemErrorCodeEnum;
import com.xbongbong.pro.opportunity.pojo.dto.OpportunityUpdateBatchDTO;
import com.xbongbong.pro.opportunity.pojo.dto.OpportunityUpdateDTO;
import com.xbongbong.saas.constant.StringConstant;
import com.xbongbong.saas.domain.entity.OpportunityStageLogEntity;
import com.xbongbong.saas.domain.entity.SaleStageEntity;
import com.xbongbong.saas.domain.entity.ext.OpportunityEntityExt;
import com.xbongbong.saas.enums.business.SalesOpportunityEnum;
import com.xbongbong.saas.model.OpportunityModel;
import com.xbongbong.saas.model.OpportunityStageLogModel;
import com.xbongbong.saas.model.SaleStageModel;
import com.xbongbong.saas.service.OpportunityService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 机会表单主数据初始化阶段id、阶段流程id、阶段比例
 *
 * @author guojun.hu
 * @version v1.0
 * @date 2021/11/18 15:15
 * @since v1.0
 */
@RestController
@RequestMapping(XbbProConstant.API_ROOT_PREFIX + "/script/stage/opportunity")
public class StageOpportunityScriptController {

    private static final Logger LOG = LoggerFactory.getLogger(StageOpportunityScriptController.class);

    @Resource
    private ProBaseConfig proBaseConfig;
    @Resource
    private SaleStageModel saleStageModel;
    @Resource
    private XbbElasticsearchRestTemplate xbbElasticsearchRestTemplate;
    @Resource
    private OpportunityService opportunityService;
    @Resource
    private OpportunityModel opportunityModel;
    @Resource
    private PaasFormExplainModel paasFormExplainModel;
    @Resource
    private OpportunityStageLogModel opportunityStageLogModel;

    /**
     * 销售机会数据刷入阶段id、阶段流程id、阶段比例
     */
    @RequestMapping(value = "/initStageOpportunityData", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public String initStageOpportunityData(@RequestBody @Valid FixDataJobDTO fixDataJobDTO, BindingResult br) throws Exception {
        if (!proBaseConfig.getXbbCode().equals(fixDataJobDTO.getXbbCode())) {
            throw new XbbException(SystemErrorCodeEnum.API_ERROR_100006);
        }
        long currentTime = DateTimeUtil.getInt();
        List<String> corpidList = fixDataJobDTO.getCorpidList();
        boolean async = Objects.equals(fixDataJobDTO.getAsync(), 1);

        // 用于查询有阶段的机会数据
        Map<String, Object> param = new HashMap<>();
        if (CollectionsUtil.isNotEmpty(corpidList)) {
            param.put("corpidIn", corpidList);
        }
        param.put("del", 0);
        param.put("flowStatusIn", Arrays.asList(FlowStatusEnum.NOT_NEED_APPROVAL.getType(), FlowStatusEnum.PASS.getType()));
        // 存在SalesOpportunityEnum.SALE_STAGE字段
        // SalesOpportunityEnum.SALE_STAGE字段的值不为空
        // 查询机会总数
        long totalCount = opportunityModel.getEntitysCount(param);
        LOG.warn("开始初始化销售机会数据，一共{}条", totalCount);

        // 用于存储已经查询出来的阶段数据
        Map<String, SaleStageEntity> saleStageMap = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        // 查询需要初始化的阶段
        Map<String, Object> stageParam = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        stageParam.put("del", 0);
        if (CollectionsUtil.isNotEmpty(corpidList)) {
            stageParam.put("corpidIn", corpidList);
        }
        int stageTotalCount = saleStageModel.getEntitysCount(stageParam);
        int stagePageSize = 10000;
        int stagePageNum = stageTotalCount % stagePageSize > 0 ? stageTotalCount / stagePageSize + 1 : stageTotalCount / stagePageSize;
        for (int currentStagePage = 1; currentStagePage <= stagePageNum; currentStagePage++) {
            stageParam.put("start", (currentStagePage - 1) * stagePageSize);
            stageParam.put("pageNum", stagePageSize);
            List<SaleStageEntity> saleStageList = saleStageModel.findEntitys(stageParam);
            LOG.warn("开始查询第{}页阶段，总共{}页，查询进度{}/{}，当前页查询到机会阶段{}条", currentStagePage, stagePageNum, currentStagePage, stagePageNum, saleStageList.size());
            // 获取本次查询得到的机会阶段数据
            Map<String, SaleStageEntity> partStageMap = saleStageList.stream().collect(Collectors.toMap(
                    stage -> stage.getCorpid() + "_" + stage.getFormId() + "_" + stage.getCode(), Function.identity()));
            // 将本次查询得到的机会阶段数据加入所有机会阶段数据中
            saleStageMap.putAll(partStageMap);
        }

        // 用于存储已经初始化完的机会数据id，最后可以和totalCount做一个相互印证
        List<Long> finishedOpportunityIdSet = new ArrayList<>();
        // 用于存储没有初始化的机会数据id，最后可以和totalCount做一个相互印证
        List<Long> exceptionOpportunityIdSet = new ArrayList<>();

        // 每1000条数据进行分页处理，计算分页页数
        int pageSize = Objects.nonNull(fixDataJobDTO.getPageSize()) ? fixDataJobDTO.getPageSize() : 10000;
        int pageNum = (int) (totalCount % pageSize > 0 ? totalCount / pageSize + 1 : totalCount / pageSize);
        for (int currentPage = 1; currentPage <= pageNum; currentPage++) {
            int startIndex = (currentPage - 1) * pageSize;
            int endIndex = currentPage * pageSize;
            if (Objects.equals(currentPage, pageNum)) {
                endIndex = (int) totalCount;
            }

            param.put("start", (currentPage - 1) * pageSize);
            param.put("pageNum", pageSize);
            List<OpportunityEntityExt> opportunityList = opportunityModel.findEntitys(param);
            LOG.warn("开始初始化第{}——{}条数据，本页查询到机会数据{}条，进度{}/{}", startIndex, endIndex, opportunityList.size(), currentPage, pageNum);

            List<OpportunityUpdateDTO> opportunityUpdateList = new ArrayList<>();
            for (OpportunityEntityExt opportunity : opportunityList) {
                Long dataId = opportunity.getDataId();
                String stageCode = opportunity.getData().getString(SalesOpportunityEnum.SALE_STAGE.getAttr());
                if (StringUtil.isEmpty(stageCode)) {
                    exceptionOpportunityIdSet.add(dataId);
                    continue;
                }
                String uniqueKey = opportunity.getCorpid() + "_" + opportunity.getFormId() + "_" + stageCode;
                SaleStageEntity saleStage = saleStageMap.get(uniqueKey);
                // 是否能够匹配到对应的阶段
                if (Objects.isNull(saleStage)) {
                    // LOG.warn("该销售机会没有找到对应的销售阶段，dataId={}，corpid={}，formId={}，stageCode={}", dataId, opportunity.getCorpid(), opportunity.getFormId(), stageCode);
                    // 这类数据怎么处理。该销售机会没有找到对应的销售阶段，dataId=107，corpid=ding18b527cbc48f835535c2f4657eb6378f，formId=2093，stageCode=9
                    exceptionOpportunityIdSet.add(dataId);
                    continue;
                }
                OpportunityUpdateDTO opportunityUpdateDTO = new OpportunityUpdateDTO();
                opportunityUpdateDTO.setId(dataId);
                opportunityUpdateDTO.setCorpid(opportunity.getCorpid());
                JSONObject updateData = new JSONObject();
                updateData.put(SalesOpportunityEnum.STAGE_ID.getAttr(), saleStage.getId());
                updateData.put(SalesOpportunityEnum.STAGE_PROCESS_ID.getAttr(), saleStage.getStageProcessId());
                updateData.put(SalesOpportunityEnum.STAGE_RATIO.getAttr(), saleStage.getOutProportion());
                opportunityUpdateDTO.setData(updateData);
                opportunityUpdateDTO.setUpdateTime(opportunity.getUpdateTime());
                opportunityUpdateList.add(opportunityUpdateDTO);
                finishedOpportunityIdSet.add(dataId);
            }
            if (CollectionsUtil.isNotEmpty(opportunityUpdateList)) {
                OpportunityUpdateBatchDTO opportunityUpdateBatchDTO = new OpportunityUpdateBatchDTO();
                opportunityUpdateBatchDTO.setOpportunityList(opportunityUpdateList);
                opportunityService.updateBatch4Script(opportunityUpdateBatchDTO, async);
            }
        }

        LOG.warn("查询到需要初始化的销售机会数据一共{}条，其中正确处理了{}条，没有处理的异常数据{}条", totalCount, finishedOpportunityIdSet.size(), exceptionOpportunityIdSet.size());
        LOG.warn("销售机会数据初始化完成，耗时{}", DateTimeUtil.getInt() - currentTime);
        return StringConstant.SUCCESS;
    }


    /**
     * 销售机会历史数据刷入阶段停留时长
     */
    @RequestMapping(value = "/initStageStayTimeOpportunityData", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public String initStageStayTimeOpportunityData(@RequestBody @Valid FixDataJobDTO fixDataJobDTO, BindingResult br) throws Exception {
        if (!proBaseConfig.getXbbCode().equals(fixDataJobDTO.getXbbCode())) {
            throw new XbbException(SystemErrorCodeEnum.API_ERROR_100006);
        }
        long currentTime = DateTimeUtil.getInt();
        boolean async = Objects.equals(fixDataJobDTO.getAsync(), 1);
        String corpid = fixDataJobDTO.getCorpid();
        Long formId = fixDataJobDTO.getFormId();
        // 需要按照corpid和formId刷历史数据
        if (StringUtil.isEmpty(corpid) || Objects.isNull(formId)) {
            throw new XbbException(SystemErrorCodeEnum.API_ERROR_100001, "corpid为空或者formId为空");
        }

        // 获取表单解释中对应的总停留时长和当前阶段停留时长字段，刷数据时字段必须存在
        PaasFormExplainEntity paasFormExplainEntity = paasFormExplainModel.getByFormId(formId, corpid);
        List<FieldAttrEntity> explainList = JSONArray.parseArray(paasFormExplainEntity.getExplains(), FieldAttrEntity.class);
        FieldAttrEntity firstIntoStageTimeField = explainList.stream().filter(item -> Objects.equals(item.getFieldType(), FieldTypeEnum.FIRST_INTO_STAGE_TIME.getType())).findFirst().orElse(null);
        FieldAttrEntity nowIntoStageTimeField = explainList.stream().filter(item -> Objects.equals(item.getFieldType(), FieldTypeEnum.NOW_INTO_STAGE_TIME.getType())).findFirst().orElse(null);
        if (Objects.isNull(firstIntoStageTimeField) || Objects.isNull(nowIntoStageTimeField)) {
            throw new XbbException(SystemErrorCodeEnum.API_ERROR_100001, "需要先启用总停留时长和当前阶段停留时长字段");
        }
        String firstIntoStageTimeFieldAttr = firstIntoStageTimeField.getAttr();
        String nowIntoStageTimeFieldAttr = nowIntoStageTimeField.getAttr();

        // 用于查询机会数据
        Map<String, Object> param = new HashMap<>();
        param.put("corpid", corpid);
        param.put("formId", formId);
        param.put("del", 0);
        param.put("flowStatusIn", Arrays.asList(FlowStatusEnum.NOT_NEED_APPROVAL.getType(), FlowStatusEnum.PASS.getType()));
        // 查询机会总数
        long totalCount = opportunityModel.getEntitysCount(param);
        LOG.warn("开始初始化销售机会数据，一共{}条", totalCount);

        // 用于存储已经初始化完的机会数据id，最后可以和totalCount做一个相互印证
        Set<Long> finishedOpportunityIdSet = new HashSet<>();
        // 用于存储没有初始化的机会数据id，最后可以和totalCount做一个相互印证
        Set<Long> exceptionOpportunityIdSet = new HashSet<>();

        // 默认每10000条数据进行分页处理，计算分页页数
        int pageSize = Objects.nonNull(fixDataJobDTO.getPageSize()) ? fixDataJobDTO.getPageSize() : 10000;
        int pageNum = (int) (totalCount % pageSize > 0 ? totalCount / pageSize + 1 : totalCount / pageSize);
        for (int currentPage = 1; currentPage <= pageNum; currentPage++) {
            int startIndex = (currentPage - 1) * pageSize;
            int endIndex = currentPage * pageSize;
            if (Objects.equals(currentPage, pageNum)) {
                endIndex = (int) totalCount;
            }
            param.put("start", (currentPage - 1) * pageSize);
            param.put("pageNum", pageSize);
            List<OpportunityEntityExt> opportunityList = opportunityModel.findEntitys(param);
            LOG.warn("开始初始化第{}——{}条数据，本页查询到机会数据{}条，进度{}/{}", startIndex, endIndex, opportunityList.size(), currentPage, pageNum);

            Set<String> stageCodeList = new HashSet<>();
            Set<Long> stageProcessIdList = new HashSet<>();
            Set<Long> stageIdList = new HashSet<>();
            List<Long> dataIdList = new ArrayList<>();
            for (OpportunityEntityExt opportunity : opportunityList) {
                String stageCode = opportunity.getData().getString(SalesOpportunityEnum.SALE_STAGE.getAttr());
                if (StringUtil.isNotEmpty(stageCode)) {
                    stageCodeList.add(stageCode);
                }
                Long stageProcessId = opportunity.getData().getLong(SalesOpportunityEnum.STAGE_PROCESS_ID.getAttr());
                if (Objects.nonNull(stageProcessId)) {
                    stageProcessIdList.add(stageProcessId);
                }
                Long stageId = opportunity.getData().getLong(SalesOpportunityEnum.STAGE_ID.getAttr());
                if (Objects.nonNull(stageId)) {
                    stageIdList.add(stageId);
                }
                dataIdList.add(opportunity.getId());
            }
            Map<String, Object> stageLogParam = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
            stageLogParam.put("corpid", corpid);
            stageLogParam.put("dataIdIn", dataIdList);
            stageLogParam.put("stageCodeIn", stageCodeList);
            stageLogParam.put("stageProcessIdIn", stageProcessIdList);
            stageLogParam.put("stageIdIn", stageIdList);
            stageLogParam.put("del", 0);
            stageLogParam.put("formId", formId);
            List<OpportunityStageLogEntity> stageLogList = opportunityStageLogModel.findEntitys(stageLogParam);

            // 将stageLog记录按照AddTime升序，并且按照StageProcessId分组
            Map<String, List<OpportunityStageLogEntity>> stageLogMap = stageLogList.stream()
                    .sorted(Comparator.comparing(OpportunityStageLogEntity::getAddTime))
                    .collect(Collectors.groupingBy(item -> item.getCorpid() + "_" + item.getFormId() + "_" + item.getDataId() + "_" + item.getStageProcessId()));

            List<OpportunityUpdateDTO> opportunityUpdateList = new ArrayList<>();
            for (OpportunityEntityExt opportunity : opportunityList) {
                Long dataId = opportunity.getDataId();
                String stageCode = opportunity.getData().getString(SalesOpportunityEnum.SALE_STAGE.getAttr());
                Long stageProcessId = opportunity.getData().getLong(SalesOpportunityEnum.STAGE_PROCESS_ID.getAttr());
                Long stageId = opportunity.getData().getLong(SalesOpportunityEnum.STAGE_ID.getAttr());
                Long oldFirstIntoStageTime = opportunity.getData().getLong(firstIntoStageTimeFieldAttr);
                Long oldNowIntoStageTime = opportunity.getData().getLong(nowIntoStageTimeFieldAttr);

                // 总停留时长、当前阶段停留时长已经存在主数据中，说明不是历史数据，不用处理
                if (Objects.nonNull(oldFirstIntoStageTime) && Objects.nonNull(oldNowIntoStageTime)) {
                    exceptionOpportunityIdSet.add(dataId);
                    continue;
                }
                // 主数据还没有关联阶段，不用处理
                if (Objects.isNull(stageProcessId) || Objects.isNull(stageId) || StringUtil.isEmpty(stageCode)) {
                    exceptionOpportunityIdSet.add(dataId);
                    continue;
                }
                String uniqueKey = corpid + "_" + formId + "_" + dataId + "_" + stageProcessId;
                List<OpportunityStageLogEntity> currentDataStageLogList = stageLogMap.get(uniqueKey);
                // 主数据上有关联阶段的信息，但是stageLog没有查到，应该是异常数据，没有stageLog记录没法进行处理
                if (CollectionsUtil.isEmpty(currentDataStageLogList)) {
                    exceptionOpportunityIdSet.add(dataId);
                    continue;
                }

                // 第一条stageLog的时间作为首次进入时间
                Long firstIntoStageTime = currentDataStageLogList.get(0).getAddTime();
                // 获取当前阶段对应的stageLog
                OpportunityStageLogEntity currentStageLog = currentDataStageLogList.stream()
                        .filter(stageLog -> Objects.equals(stageLog.getIsNow(), 1) && Objects.equals(stageLog.getStageId(), stageId) && Objects.equals(stageLog.getStageCode(), stageCode))
                        .findFirst().orElse(null);
                Long nowIntoStageTime = null;
                if (Objects.nonNull(currentStageLog)) {
                    // 获取当前阶段的进入时间
                    nowIntoStageTime = currentStageLog.getIntoTime();
                }

                OpportunityUpdateDTO opportunityUpdateDTO = new OpportunityUpdateDTO();
                opportunityUpdateDTO.setId(dataId);
                opportunityUpdateDTO.setCorpid(opportunity.getCorpid());
                JSONObject updateData = new JSONObject();
                if (Objects.isNull(oldFirstIntoStageTime) && Objects.nonNull(firstIntoStageTime)) {
                    updateData.put(firstIntoStageTimeFieldAttr, firstIntoStageTime);
                }
                if (Objects.isNull(oldNowIntoStageTime) && Objects.nonNull(nowIntoStageTime)) {
                    updateData.put(nowIntoStageTimeFieldAttr, nowIntoStageTime);
                }
                opportunityUpdateDTO.setData(updateData);
                // 脚本不更新原数据的UpdateTime
                opportunityUpdateDTO.setUpdateTime(opportunity.getUpdateTime());
                opportunityUpdateList.add(opportunityUpdateDTO);
                finishedOpportunityIdSet.add(dataId);
            }
            if (CollectionsUtil.isNotEmpty(opportunityUpdateList)) {
                OpportunityUpdateBatchDTO opportunityUpdateBatchDTO = new OpportunityUpdateBatchDTO();
                opportunityUpdateBatchDTO.setOpportunityList(opportunityUpdateList);
                opportunityService.updateBatch4Script(opportunityUpdateBatchDTO, async);
            }
        }

        LOG.warn("查询到需要初始化的销售机会数据一共{}条，其中正确处理了{}条，不需要处理的数据{}条", totalCount, finishedOpportunityIdSet.size(), exceptionOpportunityIdSet.size());
        LOG.warn("不需要处理的数据的原因可能是数据中不存在stageProcessId或stageId或stageCode或找不到对应的stageLog记录或不是历史数据");
        LOG.warn("销售机会数据初始化完成，耗时{}", DateTimeUtil.getInt() - currentTime);
        return StringConstant.SUCCESS;
    }
}
